package com.imooc.filter;

import com.google.gson.Gson;
import com.imooc.result.GraceJSONResult;
import com.imooc.result.ResponseStatusEnum;
import com.imooc.utils.IPUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * ip黑名单网关限流
 * 如果用户不停的刷新saas登录首页, 就会不停的触发/saas/getQRToken,不合理,
 * 所以要使用ip黑名单网关限流,如果某个ip在20秒内访问/saas/getQRToken超过3次,
 * 就将这个ip关进小黑屋,限制30秒之后才能继续访问;
 * @author liansen
 * @create 02-09-16:29
 */
@Component
public class IPLimitFilter implements GlobalFilter, Ordered {

    @Autowired
    private ExcludeUrlProperties excludeUrlProperties;

    @Autowired
    private StringRedisTemplate redisTemplate;

    //路径规则匹配器
    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    //ip连续请求的次数 3
    @Value("${blankIP.continueCounts}")
    private Integer continueCounts;

    //ip判断的时间间隔 20s
    @Value("${blankIP.timeInterval}")
    private Integer timeInterval;

    //限制Ip的时间 30s
    @Value("${blankIP.limitTimes}")
    private Integer limitTimes;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //获取当前请求路径, 判断当前路径是不是/saas/getQRToken, 如果是, 就拦住
        String currentPath = exchange.getRequest().getURI().getPath();
        //获取要被拦截的路径
        List<String> ipLimitUrls = excludeUrlProperties.getIpLimitUrls();
        if(!CollectionUtils.isEmpty(ipLimitUrls)){
            for(String ipLimitUrl : ipLimitUrls){
                if(antPathMatcher.match(ipLimitUrl, currentPath)){
                    //拦截并校验
                    System.out.println("被IPLimit过滤器拦住的路径:" + currentPath);
                    return doLimit(exchange, chain);
                }
            }
        }
        //放行
        return chain.filter(exchange);
    }

    public Mono<Void> doLimit(ServerWebExchange exchange, GatewayFilterChain chain){
        //获取当前请求的ip
        ServerHttpRequest request = exchange.getRequest();
        //当前ip
        String currentIP = IPUtil.getIP(request);
        /**
         * 判断当前ip在不在黑名单中, 如果在,直接返回错误提示信息;
         * (如果redis中的过期时间大于0,证明当前ip还在黑名单中)
         */
        //正常ip
        String ipRedisKey = "gateway-ip:" + currentIP;
        //被关进黑名单中的ip
        String ipRedisLimitKey = "gateway-ip-limit:" + currentIP;
        //获取剩余的限制时间
        Long limitTime = redisTemplate.getExpire(ipRedisLimitKey);
        //如果还有剩余时间,证明当前ip还在黑名单中, 直接返回错误提示信息
        if(limitTime > 0){
            return rebderErrorMsg(exchange);
        }
        //累加当前ip的访问次数存到redis中,如果查不到,就会先将ipRedisKey的值初始化为0再加1;
        Long requestCounts = redisTemplate.opsForValue().increment(ipRedisKey, 1);
        //如果当前ip是第一次访问, 就设置过期时间20s
        if(requestCounts == 1){
            redisTemplate.expire(ipRedisKey, timeInterval, TimeUnit.SECONDS);
        }

        //如果累积的次数大于连续访问的最大限制次数
        if(requestCounts > continueCounts){
            //将本ip关进小黑屋,并设置过期时间30s; 30s之后才能正常访问
            redisTemplate.opsForValue().set(ipRedisLimitKey, ipRedisLimitKey, limitTimes, TimeUnit.SECONDS);
            //返回错误提示信息
            return rebderErrorMsg(exchange);
        }
        //放行
        return chain.filter(exchange);
    }

    public Mono<Void> rebderErrorMsg(ServerWebExchange exchange){
        ServerHttpResponse response = exchange.getResponse();
        //设置状态码为500
        response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
        //请求过于频繁
        GraceJSONResult jsonResult = GraceJSONResult.exception(ResponseStatusEnum.SYSTEM_ERROE_BLANK_IP);
        //设置返回json类型
        // 设置header类型为 Content-type: application/json
        if(!response.getHeaders().containsKey("Content-type")){
            response.getHeaders().add("Content-type", MimeTypeUtils.APPLICATION_JSON_VALUE);
        }
        //转换json字符串, 并向response中写入数据
        String jsonResultString = new Gson().toJson(jsonResult);
        DataBuffer buffer = response
                .bufferFactory()
                .wrap(jsonResultString.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(buffer));
    }

    //设置过滤器的执行顺序
    @Override
    public int getOrder() {
        return 1;
    }
}
